//
// Created by liutp on 2022/6/19.
//

#include "type.h"

#ifdef __cplusplus
extern "C"
{
#endif

static int read_data_from_stream(void *opaque, uint8_t *buf, int buf_size);

static int decodePacket(Stream *s);

static char errStr[1024] = {0};

const char *ffmpeg_error(int error) {
    int e = av_strerror(error, errStr, sizeof(errStr));
    if (e < 0) {
        snprintf(errStr, sizeof(errStr), "unknown errno: %d", error);
    }
    return errStr;

}

//ffmpeg_init 初始化ffmpeg并返回Stream对象
Stream *ffmpeg_init(int codecID) {
    const AVCodec *codec = avcodec_find_decoder((enum AVCodecID) (codecID) /*AV_CODEC_ID_H264 27*/);
    if (!codec) {
        fprintf(stderr, "找不到编码器 {%s}\n", avcodec_get_name((enum AVCodecID) (codecID)));
        return NULL;
    }

    Stream *s = (Stream *) malloc(sizeof(Stream));
    s->state = 19;
    s->codecID = (enum AVCodecID) (codecID);
    s->displayNumber = 0;
    s->width = 1;
    s->height = 1;
    // s->yuv_data = (u8 *) malloc(s->width * s->height * 3);
    s->yuv_data = NULL;

    {
        s->codec_ctx = avcodec_alloc_context3(codec);
        s->codec_ctx->bit_rate = 500 * 1024;
        s->codec_ctx->width = s->width;
        s->codec_ctx->height = s->height;
        s->codec_ctx->time_base.den = 25;
        s->codec_ctx->time_base.num = 1;
        s->codec_ctx->gop_size = 7;
        s->codec_ctx->max_b_frames = 1;
        s->codec_ctx->thread_count = 1;
//        s->codec_ctx->pix_fmt = (AVPixelFormat) pix_fmt; // AV_PIX_FMT_YUV444P;  //5
//        s->codec_ctx->profile = profile;                // FF_PROFILE_H264_HIGH_444;    //144
        if (avcodec_open2(s->codec_ctx, codec, NULL) < 0) {
            fprintf(stderr, "Could not open codec\n");
            return NULL;
        }
    }

    {
        s->format_ctx = avformat_alloc_context();
        if (s->codecID == AV_CODEC_ID_H264) {
            s->format_ctx->iformat = av_find_input_format("h264");
        }
        if (s->format_ctx->iformat == NULL) {
            fprintf(stderr, "unknown av_find_input_format %d\n", s->codecID);
            return NULL;
        } else {
            fprintf(stdout, "input_format> %s\n", s->format_ctx->iformat->long_name);
        }

        int mallocBufferSize = 32000;
        s->mallocBuffer = (unsigned char *) av_malloc(mallocBufferSize);
        s->format_ctx->pb = avio_alloc_context(s->mallocBuffer, mallocBufferSize, 0, s, read_data_from_stream, NULL,
                                               NULL);
    }

    {
        s->pFrame = av_frame_alloc();
//        s->pFrame->format = s->codec_ctx->pix_fmt;
    }

    {
        s->pPacket = av_packet_alloc();
    }
    return s;
}

void ffmpeg_dump(Stream *s) {
    fprintf(stdout, "视频数据 codecID: %d,  width: %d,  height: %d,  pix_fmt: %d,  profile: %d \n", s->codecID, s->width,
            s->height, s->codec_ctx->pix_fmt, s->codec_ctx->profile);
}

//ffmpeg_release 释放Stream对象
int ffmpeg_release(Stream *s) {
    if (s == NULL) {
        return -1;
    }
    if (s->state == 0) {
        return -2;
    }
    s->state = 0;
    if (s->yuv_data != NULL) {
        free(s->yuv_data);
        s->yuv_data = NULL;
    }
    avcodec_free_context(&s->codec_ctx);
    avformat_free_context(s->format_ctx);
    if (s->pFrame) {
        av_frame_free(&s->pFrame);
    }
    if (s->pPacket) {
        av_packet_free(&s->pPacket);
    }
    free(s);
    return 0;
}

//ffmpeg_input_data 为Stream对象添加数据
int ffmpeg_input_data(Stream *s, u8 *data, u32 length) {
    s->buffer = data; //(u8 *)malloc(sizeof(u8) * length);
    s->pos = s->buffer;
    s->length = length;
    s->end = s->buffer + length;
    fprintf(stdout, "ffmpeg 初始化数据 %d \n", s->length);

    int error = avformat_open_input(&s->format_ctx, "", s->format_ctx->iformat, NULL);
    if (error != 0) {
        fprintf(stderr, "avformat_open_input: %d(%s) \n", error, ffmpeg_error(error));
        return error;
    }
    fprintf(stdout, "ffmpeg 初始化数据 成功 \n");

    return 0;
}

//ffmpeg_decode_frame 解码1帧Stream数据
int ffmpeg_decode_frame(Stream *s) {
    int error = 0;
    while (true) {
        if (s->pPacket != NULL) {
            error = av_read_frame(s->format_ctx, s->pPacket);
            if (error != 0) {
                if (AVERROR_EOF == error) {
                    fprintf(stdout, "av_read_frame读取数据结束\n");
                } else {
                    fprintf(stderr, "av_read_frame: %d(%s)\n", error, ffmpeg_error(error));
                }
                av_packet_free(&s->pPacket);
                s->pPacket = NULL;
                break;
            } else {
                error = avcodec_send_packet(s->codec_ctx, s->pPacket);
                if (error != 0) {
                    if (AVERROR_EOF != error) {
                        printf("avcodec_send_packet: %d(%s)\n", error, ffmpeg_error(error));
                    }
                }
            }
        }

        error = avcodec_receive_frame(s->codec_ctx, s->pFrame);
        if (error != 0) {
            if (AVERROR_EOF != error) {
                printf("avcodec_receive_frame: %d(%s) \n", error, ffmpeg_error(error));
            }
            if (s->pPacket == NULL) {
                break;
            }
        } else {
            error = decodePacket(s);
            if (error != 0) {
                printf("decodePacket error: %d %d(%s)\n", s->displayNumber, error, ffmpeg_error(error));
            }
            return error;
        }
    }
    return -1;
}

static int read_data_from_stream(void *opaque, uint8_t *buf, int buf_size) {
    auto *stream = (Stream *) opaque;
    if (buf_size > stream->end - stream->pos) {
        buf_size = stream->end - stream->pos;
    }
    if (buf_size == 0) {
        // printf("读入数据结束\n");
        return AVERROR_EOF;
    }
    // printf("读入数据 %d\n", buf_size);
    memcpy(buf, stream->pos, buf_size);
    stream->pos += buf_size;
    return buf_size;
}

static void yuv420p(Stream *s) {
    u32 width = s->width;
    u32 height = s->height;
    if (s->yuv_data == NULL) {
        s->yuv_data = (u8 *) malloc(width * height * 3);
    }

    s->size_y = s->pFrame->linesize[0];
    s->size_u = s->pFrame->linesize[1];
    s->size_v = s->pFrame->linesize[2];
    s->data_y = s->pFrame->data[0];
    s->data_u = s->pFrame->data[1];
    s->data_v = s->pFrame->data[2];
//    u8 *y = s->yuv_data;
//    u8 *u = y + height * width;
//    u8 *v = u + height * width / 4;
//    memcpy(y, s->pFrame->data[0], height * width);
//    memcpy(u, s->pFrame->data[1], height * width / 4);
//    memcpy(v, s->pFrame->data[2], height * width / 4);
//    printf("Succeed to decode yuv420p %d frame %d [%d,%d]!\n", s->displayNumber, s->pFrame->format, width, height);
}

static void yuv444p(Stream *s) {
    int i;
    int width = s->width = s->pFrame->width;
    int height = s->height = s->pFrame->height;
    if (s->yuv_data == NULL) {
        s->yuv_data = (u8 *) malloc(width * height * 3);
    }

    int linesize_y = s->pFrame->linesize[0];
    int linesize_u = s->pFrame->linesize[1];
    int linesize_v = s->pFrame->linesize[2];
    u8 *y = s->yuv_data;
    u8 *u = y + height * width;
    u8 *v = u + height * width;
    memcpy(y, s->pFrame->data[0], height * width);
    memcpy(u, s->pFrame->data[1], height * width);
    memcpy(v, s->pFrame->data[2], height * width);
//    printf("Succeed to decode yuv444p %d frame %d [%d,%d,%d]!\n", s->displayNumber, s->pFrame->format, linesize_y,
//           linesize_u, linesize_v);
}

static int decodePacket(Stream *s) {
    s->format = s->pFrame->format;
    s->width = s->pFrame->width;
    s->height = s->pFrame->height;
    switch (s->pFrame->format) {
        case AV_PIX_FMT_YUV420P:
            yuv420p(s);
            break;
        case AV_PIX_FMT_YUV444P:
            yuv444p(s);
            break;

        default:
            fprintf(stderr, "Error while decoding frame unknow format %d\n", s->pFrame->format);
            return -1;
            break;
    }
    s->displayNumber++;
    return 0;
}

#ifdef __cplusplus
}
#endif